﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

namespace MicroDal.Providers
{
    internal class SqlClientResource : IDbResource
    {
        private SqlConnection _connection;
        private bool _disposed;

        public SqlClientResource(string connectionString)
        {
            _connection = new SqlConnection(connectionString);
            _connection.FireInfoMessageEventOnUserErrors = false;
            _connection.InfoMessage += (s, ea) =>
            {
                Tracer.WriteMessage(ea.Message);
            };
            _disposed = false;
        }
        #region IDbResource Members

        public object ExecuteScalar(string sql, CommandType commandType, IEnumerable<DbParameter> parameters)
        {
            if (_disposed)
                throw new ObjectDisposedException("SqlClientResource was disposed.");

            using (var command = new SqlCommand(sql, _connection, _tx))
            {
                command.CommandType = commandType;
                foreach (SqlParameter parameter in parameters)
                    command.Parameters.Add(parameter);

                if (_connection.State != ConnectionState.Open)
                    _connection.Open();

                return command.ExecuteScalar();
            }
        }

        public int Execute(string sql, CommandType commandType, IEnumerable<DbParameter> parameters)
        {
            if (_disposed)
                throw new ObjectDisposedException("SqlClientResource was disposed.");

            using (var command = new SqlCommand(sql, _connection, _tx))
            {
                command.CommandType = commandType;
                foreach (SqlParameter parameter in parameters)
                    command.Parameters.Add(parameter);

                if (_connection.State != ConnectionState.Open)
                    _connection.Open();

                return command.ExecuteNonQuery();
            }
        }

        public IEnumerable<T> Fetch<T>(string sql, CommandType commandType, IEnumerable<DbParameter> parameters, Func<DbDataReader, T> mapper)
        {
            if (_disposed)
                throw new ObjectDisposedException("SqlClientResource was disposed.");

            using (var command = new SqlCommand(sql, _connection, _tx))
            {
                command.CommandType = commandType;

                foreach (SqlParameter parameter in parameters)
                    command.Parameters.Add(parameter);

                if (_connection.State != ConnectionState.Open)
                    _connection.Open();

                return FetchInternal(command, mapper);
            }
        }

        private IEnumerable<T> FetchInternal<T>(SqlCommand command, Func<DbDataReader, T> mapper)
        {
            SqlDataReader reader = command.ExecuteReader();
            if (!reader.HasRows)
            {
                reader.Close();
                yield break;
            }

            while (reader.Read())
            {
                yield return mapper(reader);
            }
            reader.Close();
        }

        public T FetchOne<T>(string sql, CommandType commandType, IEnumerable<DbParameter> parameters, Func<DbDataReader, T> mapper)
        {
            if (_disposed)
                throw new ObjectDisposedException("SqlClientResource was disposed.");

            using (var command = new SqlCommand(sql, _connection, _tx))
            {
                command.CommandType = commandType;

                foreach (SqlParameter parameter in parameters)
                    command.Parameters.Add(parameter);

                if (_connection.State != ConnectionState.Open)
                    _connection.Open();


                SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow);

                T result = reader.Read() ? mapper(reader) : default(T);

                reader.Close();
                return result;
            }
        }

        public T GetLastInsertedId<T>()
        {
            if (_disposed)
                throw new ObjectDisposedException("SqlClientResource was disposed.");

            string sql;
            switch (typeof(T).Name)
            {
                case "Int32":
                    sql = "SELECT CAST(@@IDENTITY AS int)";
                    break;
                case "Int64":
                    sql = "SELECT CAST(@@IDENTITY AS bigint)";
                    break;
                case "Int16":
                    sql = "SELECT CAST(@@IDENTITY AS smallint)";
                    break;
                case "Byte":
                    sql = "SELECT CAST(@@IDENTITY AS tinyint)";
                    break;
                case "Decimal":
                    sql = "SELECT @@IDENTITY";
                    break;
                default:
                    throw new ArgumentException("Type: " + typeof(T).Name + " is not supported.");
            }
            using (var command = new SqlCommand(sql, _connection, _tx))
            {
                if (_connection.State != ConnectionState.Open)
                    return default(T);

                return (T)command.ExecuteScalar();
            }
        }


        public Func<ParameterConfig, object, DbParameter> ParameterInitializer
        {
            get
            {
                return (config, val) => new SqlParameter(config.Name, val)
                {
                    Direction = config.Direction
                };
            }
        }

        public string ParameterPattern { get { return @"[^@]?(?<name>@\w+)"; } }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_connection != null)
                        _connection.Dispose();
                    Tracer.WriteTrace("SqlClientResource disposed.");
                }
                _connection = null;
                _disposed = true;
            }
        }
        #endregion

        SqlTransaction _tx;
        public DbTransaction GetTransaction(IsolationLevel isolationLevel)
        {
            if (_connection.State != ConnectionState.Open)
                _connection.Open();

            _tx = _connection.BeginTransaction(isolationLevel);
            return _tx;
        }
    }
}
